﻿using Microsoft.Practices.ESB.Exception.Management;
using Microsoft.Practices.ESB.Itinerary;
using Microsoft.Practices.ESB.Itinerary.Services;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.Bam.EventObservation;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.XLANGs.BaseTypes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Xsl;
using System.Xml.XPath;

namespace BizTalk.ESB.Extensions.Helpers
{
    public class ItineraryServiceDataMappingConfig
    {
        public Dictionary<string, string> DataMappings { get; set; }

        public void AddDataTrackingPoint(string DataFieldName, string DataFieldValue)
        {
            if (DataMappings == null)
            {
                DataMappings = new Dictionary<string, string>();
            }
            DataMappings.Add(DataFieldName, DataFieldValue);
        }

        public string GetMappingSourceValue(string SourceKey, XmlDocument Message, IBaseMessageContext Context)
        {
            string output = string.Empty;
            if (!string.IsNullOrEmpty(SourceKey))
            {
                if (SourceKey.Contains("{"))
                {
                    var key = SourceKey.TrimEnd("}".ToCharArray());
                    var keyPrefix = key.Substring(0, key.IndexOf("{"));
                    var keyValue = key.Substring(key.IndexOf("{") + 1);
                    switch (keyPrefix)
                    {
                        case "XPATH":
                            {
                                if (IsValidXpath(keyValue))
                                {
                                    var n = Message.SelectSingleNode(keyValue);
                                    if (n == null)
                                    {
                                        throw new ArgumentNullException(keyValue, "The xpath provided returned a null node");
                                    }
                                    else
                                    {
                                        output = n.InnerText;
                                    }
                                }
                                break;
                            }
                        case "CONTEXT":
                            {
                                output = GetContextPropertyValue(Context, keyValue);
                                break;
                            }
                        case "SSO":
                            {
                                output = GetSsoConfigValue(keyValue);
                                break;
                            }
                        default:
                            {
                                output = keyValue;
                                break;
                            }
                    }
                }
                else
                {
                    switch (SourceKey)
                    {
                        case "NOW":
                            {
                                output = DateTime.UtcNow.ToString();
                                break;
                            }
                        case "GUID":
                            {
                                output = Guid.NewGuid().ToString();
                                break;
                            }
                        default:
                            {
                                output = SourceKey;
                                break;
                            }
                    }
                }

            }
            else
            {
                EventLogger.Write("Encountered an invalid Data Mapping configuration: " + SourceKey, MethodInfo.GetCurrentMethod());
            }
            return output;
        }

        private string GetMappingSourceValue(string SourceKey, XLANGMessage Message, XmlDocument XmlMessage)
        {
            string output = string.Empty;
            if (!string.IsNullOrEmpty(SourceKey))
            {
                if (SourceKey.Contains("{"))
                {
                    var key = SourceKey.TrimEnd("}".ToCharArray());
                    var keyPrefix = key.Substring(0, key.IndexOf("{"));
                    var keyValue = key.Substring(key.IndexOf("{") + 1);
                    switch (keyPrefix)
                    {
                        case "XPATH":
                            {
                                if (IsValidXpath(keyValue))
                                {
                                    var n = XmlMessage.SelectSingleNode(keyValue);
                                    if (n == null)
                                    {
                                        throw new ArgumentNullException(keyValue, "The xpath provided returned a null node");
                                    }
                                    else
                                    {
                                        output = n.InnerText;
                                    }
                                }
                                break;
                            }
                        case "CONTEXT":
                            {
                                output = GetContextPropertyValue(Message, keyValue);
                                break;
                            }
                        default:
                            {
                                output = keyValue;
                                break;
                            }
                    }
                }
                else
                {
                    switch (SourceKey)
                    {
                        case "NOW":
                            {
                                output = DateTime.UtcNow.ToString();
                                break;
                            }
                        case "GUID":
                            {
                                output = Guid.NewGuid().ToString();
                                break;
                            }
                        default:
                            {
                                output = SourceKey;
                                break;
                            }
                    }
                }

            }
            else
            {
                EventLogger.Write("Encountered an invalid Itinerary Data Mapping configuration: " + SourceKey, MethodInfo.GetCurrentMethod());
            }
            return output;
        }

        public Dictionary<string, string> BuildDataMappingDictionary(string DataMappingConfig, XmlDocument Message, IBaseMessageContext Context, bool StopOnError)
        {
            var config = new Dictionary<string, string>();
            if (!string.IsNullOrEmpty(DataMappingConfig))
            {
                var mappingList = DataMappingConfig.Split("&".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                foreach (string fieldMapping in mappingList)
                {
                    try
                    {
                        var fieldKey = fieldMapping.Substring(0, fieldMapping.IndexOf("="));
                        var fieldValueSource = fieldMapping.Substring(fieldMapping.IndexOf("=") + 1);
                        if (!string.IsNullOrEmpty(fieldKey) && !string.IsNullOrEmpty(fieldValueSource))
                        {
                            var fieldValue = GetMappingSourceValue(fieldValueSource, Message, Context);
                            config.Add(fieldKey, fieldValue);
                            System.Diagnostics.Trace.WriteLine("Added Data Mapping: Field = " + fieldKey
                                + " and Value/Mapping = " + fieldValue);
                        }
                        else
                        {
                            EventLogger.Write("Encountered an invalid Data Mapping configuration: " + fieldMapping, MethodInfo.GetCurrentMethod());
                        }
                    }
                    catch (Exception e)
                    {
                        if (e.InnerException != null)
                        {
                            System.Diagnostics.Trace.WriteLine("ERROR while evaluating Data Mapping configuration: " + fieldMapping + ". Inner Exception = " + e.InnerException.Message);
                            EventLogger.Write("Error while evaluating Data Mapping configuration: " + fieldMapping + ". Inner Exception = " + e.InnerException.Message, MethodInfo.GetCurrentMethod(), e);
                        }
                        else
                        {
                            System.Diagnostics.Trace.WriteLine("ERROR while evaluating Data Mapping configuration: " + fieldMapping);
                            EventLogger.Write("Error while evaluating Data Mapping configuration: " + fieldMapping, MethodInfo.GetCurrentMethod(), e);
                        }

                        if (StopOnError)
                        {
                            throw e;
                        }
                    }
                }
            }
            return config;
        }

        public Dictionary<string, string> BuildDataMappingDictionary(string DataMappingConfig, XLANGMessage Message, bool StopOnError)
        {
            var config = new Dictionary<string, string>();
            if (!string.IsNullOrEmpty(DataMappingConfig))
            {
                XmlDocument xmlMsg = (XmlDocument)Message[0].RetrieveAs(typeof(XmlDocument));
                var mappingList = DataMappingConfig.Split("&".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                foreach (string fieldMapping in mappingList)
                {
                    try
                    {
                        var fieldKey = fieldMapping.Substring(0, fieldMapping.IndexOf("="));
                        var fieldValueSource = fieldMapping.Substring(fieldMapping.IndexOf("=") + 1);
                        if (!string.IsNullOrEmpty(fieldKey) && !string.IsNullOrEmpty(fieldValueSource))
                        {
                            var fieldValue = GetMappingSourceValue(fieldValueSource, Message, xmlMsg);
                            config.Add(fieldKey, fieldValue);
                            System.Diagnostics.Trace.WriteLine("Added Data Mapping: Field = " + fieldKey
                                + " and Value/Mapping = " + fieldValue);
                        }
                        else
                        {
                            EventLogger.Write("Encountered an invalid Data Mapping configuration: " + fieldMapping, MethodInfo.GetCurrentMethod());
                        }
                    }
                    catch (Exception e)
                    {
                        if (e.InnerException != null)
                        {
                            System.Diagnostics.Trace.WriteLine("ERROR while evaluating Data Mapping configuration: " + fieldMapping + ". Inner Exception = " + e.InnerException.Message);
                            EventLogger.Write("Error while evaluating Data Mapping configuration: " + fieldMapping + ". Inner Exception = " + e.InnerException.Message, MethodInfo.GetCurrentMethod(), e);
                        }
                        else
                        {
                            System.Diagnostics.Trace.WriteLine("ERROR while evaluating Data Mapping configuration: " + fieldMapping);
                            EventLogger.Write("Error while evaluating Data Mapping configuration: " + fieldMapping, MethodInfo.GetCurrentMethod(), e);
                        }

                        if (StopOnError)
                        {
                            throw e;
                        }
                    }
                }
            }
            return config;
        }

        protected internal object[] BuildTrackingConfigArray(string DataMappingConfig, XmlDocument Message, IBaseMessageContext Context, bool StopOnError)
        {
            var config = new List<string>();
            if (!string.IsNullOrEmpty(DataMappingConfig))
            {
                var mappingList = DataMappingConfig.Split("&".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                foreach (string fieldMapping in mappingList)
                {
                    try
                    {
                        var fieldKey = fieldMapping.Substring(0, fieldMapping.IndexOf("="));
                        var fieldValueSource = fieldMapping.Substring(fieldMapping.IndexOf("=") + 1);
                        if (!string.IsNullOrEmpty(fieldKey) && !string.IsNullOrEmpty(fieldValueSource))
                        {
                            var fieldValue = GetMappingSourceValue(fieldValueSource, Message, Context);
                            config.Add(fieldKey);
                            config.Add(fieldValue);
                            System.Diagnostics.Trace.WriteLine("Added Data Mapping: Field = " + fieldKey
                                + " and Value/Mapping = " + fieldValue);
                        }
                        else
                        {
                            EventLogger.Write("Encountered an invalid Data Mapping configuration: " + fieldMapping, MethodInfo.GetCurrentMethod());
                        }
                    }
                    catch (Exception e)
                    {
                        if (e.InnerException != null)
                        {
                            System.Diagnostics.Trace.WriteLine("ERROR while evaluating Data Mapping configuration: " + fieldMapping + ". Inner Exception = " + e.InnerException.Message);
                            EventLogger.Write("Error while evaluating Data Mapping configuration: " + fieldMapping + ". Inner Exception = " + e.InnerException.Message, MethodInfo.GetCurrentMethod(), e);
                        }
                        else
                        {
                            System.Diagnostics.Trace.WriteLine("ERROR while evaluating Data Mapping configuration: " + fieldMapping);
                            EventLogger.Write("Error while evaluating Data Mapping configuration: " + fieldMapping, MethodInfo.GetCurrentMethod(), e);
                        }

                        if (StopOnError)
                        {
                            throw e;
                        }
                    }
                }
            }
            return config.ToArray<object>();
        }

        protected internal object[] BuildTrackingConfigArray(string DataMappingConfig, XLANGMessage Message, bool StopOnError)
        {
            var config = new List<string>();
            if (!string.IsNullOrEmpty(DataMappingConfig))
            {
                XmlDocument xmlMsg = (XmlDocument)Message[0].RetrieveAs(typeof(XmlDocument));
                var mappingList = DataMappingConfig.Split("&".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                foreach (string fieldMapping in mappingList)
                {
                    try
                    {
                        var fieldKey = fieldMapping.Substring(0, fieldMapping.IndexOf("="));
                        var fieldValueSource = fieldMapping.Substring(fieldMapping.IndexOf("=") + 1);
                        if (!string.IsNullOrEmpty(fieldKey) && !string.IsNullOrEmpty(fieldValueSource))
                        {
                            var fieldValue = GetMappingSourceValue(fieldValueSource, Message, xmlMsg);
                            config.Add(fieldKey);
                            config.Add(fieldValue);
                            System.Diagnostics.Trace.WriteLine("Added Data Mapping: Field = " + fieldKey
                                + " and Value/Mapping = " + fieldValue);
                        }
                        else
                        {
                            EventLogger.Write("Encountered an invalid Data Mapping configuration: " + fieldMapping, MethodInfo.GetCurrentMethod());
                        }
                    }
                    catch (Exception e)
                    {
                        if (e.InnerException != null)
                        {
                            System.Diagnostics.Trace.WriteLine("ERROR while evaluating Data Mapping configuration: " + fieldMapping + ". Inner Exception = " + e.InnerException.Message);
                            EventLogger.Write("Error while evaluating Data Mapping configuration: " + fieldMapping + ". Inner Exception = " + e.InnerException.Message, MethodInfo.GetCurrentMethod(), e);
                        }
                        else
                        {
                            System.Diagnostics.Trace.WriteLine("ERROR while evaluating Data Mapping configuration: " + fieldMapping);
                            EventLogger.Write("Error while evaluating Data Mapping configuration: " + fieldMapping, MethodInfo.GetCurrentMethod(), e);
                        }

                        if (StopOnError)
                        {
                            throw e;
                        }
                    }
                }
            }
            return config.ToArray<object>();
        }

        private string GetContextPropertyValue(IBaseMessageContext Context, string keyValue)
        {
            var output = string.Empty;
            if (!string.IsNullOrEmpty(keyValue))
            {
                var splitLocation = keyValue.LastIndexOf(@"/");
                var propNamespace = keyValue.Substring(0, splitLocation);
                var propName = keyValue.Substring(splitLocation + 1);

                output = (string)Context.Read(propName, propNamespace);
            }
            return output;
        }

        private string GetContextPropertyValue(XLANGMessage Message, string keyValue)
        {
            var output = string.Empty;
            if (!string.IsNullOrEmpty(keyValue))
            {
                var splitLocation = keyValue.LastIndexOf(@"/");
                var propNamespace = keyValue.Substring(0, splitLocation);
                var propName = keyValue.Substring(splitLocation + 1);

                XmlQName prop = new XmlQName(propName, propNamespace);
                output = Convert.ToString(Message.GetPropertyValue(prop.GetType()));
            }
            return output;
        }

        private bool IsValidXpath(string keyValue)
        {
            if (!string.IsNullOrEmpty(keyValue))
            {
                try
                {
                    var xpathValue = XPathExpression.Compile(keyValue);
                    return true;
                }
                catch (XPathException)
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }

        private string GetSsoConfigValue(string resolverKey)
        {
            var output = string.Empty;
            if (!string.IsNullOrEmpty(resolverKey))
            {
                var keyparts = resolverKey.Split(@",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                if (keyparts.Length == 2)
                {
                    var ssoApplication = keyparts[0];
                    var ssoKey = keyparts[1];

                    // DEBUG
                    System.Diagnostics.Trace.WriteLine(string.Format("Attempting to retrieve SSO Config: Application: {0}, Key: {1}."
                        , ssoApplication, ssoKey));
                    
                    output = (string)SSO.BizTalk.SsoConfigWrapper.TryGetConfigValue(ssoApplication, ssoKey, string.Empty);
                }
            }
            return output;
        }
    }
}
